ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / ota / dfu / LegacyDfuTransport.kt
Displaying Raw β’ Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/LegacyDfuTransport.kt 41366bb48d5dc06c1da80d2d723848ec29fb12bc (41366bb4) Text, 39.74 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(
Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffThrowsCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72package T7ee787org.meshtastic.feature.firmware.ota.dfu
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineDispatcher
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.TimeoutCancellationException
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.channels.Channel
Tff7b72import T7ee787kotlinx.coroutines.currentCoroutineContext
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.ensureActive
Tff7b72import T7ee787kotlinx.coroutines.flow.catch
Tff7b72import T7ee787kotlinx.coroutines.flow.launchIn
Tff7b72import T7ee787kotlinx.coroutines.flow.onEach
Tff7b72import T7ee787kotlinx.coroutines.withTimeout
Tff7b72import T7ee787kotlinx.coroutines.withTimeoutOrNull
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionState
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleScanner
Tff7b72import T7ee787org.meshtastic.core.ble.BleWriteType
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatching
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.calculateMacPlusOne
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.scanForBleDevice
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.withDisconnectTripwire
Tff7b72import T7ee787kotlin.concurrent.Volatile
Tff7b72import T7ee787kotlin.time.Duration
Tff7b72import T7ee787kotlin.time.Duration.Companion.milliseconds
Tff7b72import T7ee787kotlin.time.Duration.Companion.minutes
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds
Tff7b72import T7ee787kotlin.time.TimeSource
T8b949e/**
* Kable-based transport for the Nordic **Legacy DFU** protocol (Nordic SDK 11/12 / Adafruit `BLEDfu`).
*
* Most nRF52 boards in the field β including the RAK4631 with the recommended Adafruit/oltaco "OTAFIX" bootloader β
* speak Legacy DFU rather than Secure DFU. The two protocols share nothing at the upload layer:
* - Different service & characteristic UUIDs (`1530`/`1531`/`1532` vs `FE59`/`8EC9β¦`).
* - Different opcodes; init packet is sent on the Packet char between two control-point writes (vs Secure's
* CREATE/PACKET/EXECUTE object flow).
* - PRN payload is bytes-received uint32 (vs Secure's offset+CRC32).
* - No CRC32 in the protocol β image integrity relies on the device's CRC16 in the init packet.
*
* Phase-1 buttonless trigger is shared with [SecureDfuTransport] (see `triggerButtonlessDfu` there).
*/
Tff7b72class T56d364LegacyDfuTransport
Te6edf3internal Tff7b72constructorTb4b4b4(
Tff7b72private Tff7b72val Te6edf3scannerTb4b4b4: Te6edf3BleScannerTb4b4b4,
Te6edf3connectionFactoryTb4b4b4: Te6edf3BleConnectionFactoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3dispatcherTb4b4b4: Te6edf3CoroutineDispatcherTb4b4b4,
Tff7b72private Tff7b72val Te6edf3streamProfileTb4b4b4: Te6edf3LegacyDfuStreamProfile Tff7b72= Te6edf3LegacyDfuStreamProfileTb4b4b4.Te6edf3NORMALTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3DfuUploadTransport Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3transportScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3SupervisorJobTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3dispatcherTb4b4b4)
Tff7b72private Tff7b72val Te6edf3bleConnection Tff7b72= Te6edf3connectionFactoryTb4b4b4.Te6edf3createTb4b4b4(Te6edf3transportScopeTb4b4b4, Ta5d6ff"Ta5d6ffLegacy DFUTa5d6ff"Tb4b4b4)
T8b949e/**
* Stream progress captured by the disconnect-tripwire callback. The disconnect watcher runs in a child coroutine on
* the caller's dispatcher, which is IO in production but may differ in tests. These fields provide visible
* diagnostic snapshots between the streaming and watcher coroutines. A fresh transport is created per upload
* attempt, so these never leak across sessions.
*/
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3streamOffsetTb4b4b4: Tffa657Int Tff7b72= T79c0ff0
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3streamLastPrnOffsetTb4b4b4: Tffa657Int Tff7b72= Tff7b72-T79c0ff1
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3streamLastPrnLatencyMsTb4b4b4: Tffa657Long Tff7b72= Tff7b72-T79c0ff1
T8b949e/** Receives parsed responses from the Control Point characteristic. */
Tff7b72private Tff7b72val Te6edf3notificationChannel Tff7b72= Te6edf3ChannelTff7b72<Te6edf3LegacyDfuResponseTff7b72>Tb4b4b4(Te6edf3ChannelTb4b4b4.Te6edf3UNLIMITEDTb4b4b4)
T8b949e/** Name advertised by the device in DFU mode (e.g. `4631_DFU`). Captured in [connectToDfuMode]. */
Tff7b72private Tff7b72var Te6edf3dfuAdvertisedNameTb4b4b4: Tffa657String? Tff7b72= Tff7b72null
T8b949e/**
* DFU Version reported by the optional DFU Version characteristic during [connectToDfuMode]. `-1` when the
* characteristic is absent or unreadable. Captured here so the stream-start log can include it without re-reading.
*/
Tff7b72private Tff7b72var Te6edf3dfuVersionTb4b4b4: Tffa657Int Tff7b72= Tff7b72-T79c0ff1
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 2: Connect to device in DFU mode
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Scans for the device in DFU mode (address or address+1) and establishes the GATT connection, enabling
* notifications on the Control Point.
*
* Best-effort reads the optional DFU Version characteristic to gate against unsupported old (SDK β€ 6) bootloaders.
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectToDfuModeTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3safeCatching Tb4b4b4{
Tff7b72val Te6edf3dfuAddress Tff7b72= Te6edf3calculateMacPlusOneTb4b4b4(Te6edf3addressTb4b4b4)
Tff7b72val Te6edf3targetAddresses Tff7b72= Te6edf3setOfTb4b4b4(Te6edf3addressTb4b4b4, Te6edf3dfuAddressTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Scanning for DFU mode device at Tffd700$Te6edf3targetAddressesTa5d6ff...Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3device Tff7b72=
Te6edf3scanForDevice Tb4b4b4{ Te6edf3d Tff7b72-Tff7b72> Te6edf3dTb4b4b4.Te6edf3address Tff7b72in Te6edf3targetAddresses Tb4b4b4}
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffDFU mode device not found. Tried: Tffd700$Te6edf3targetAddressesTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Found DFU mode device at Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff (name=Tffd700${Te6edf3deviceTb4b4b4.Te6edf3nameTffd700}Ta5d6ff), connecting...Ta5d6ff" Tb4b4b4}
Te6edf3dfuAdvertisedName Tff7b72= Te6edf3deviceTb4b4b4.Te6edf3name
Te6edf3bleConnectionTb4b4b4.Te6edf3connectionState
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Connection state β Tffd700$Te6edf3itTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3transportScopeTb4b4b4)
Tff7b72val Te6edf3connected Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3connectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4, Te6edf3CONNECT_TIMEOUTTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3connected Tff7b72is Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffFailed to connect to DFU device Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
Tff7b72val Te6edf3subscribed Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3service
Tb4b4b4.Te6edf3observeTb4b4b4(Te6edf3controlCharTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Control Point subscribedTa5d6ff" Tb4b4b4}
Te6edf3subscribedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72>
Tff7b72val Te6edf3parsed Tff7b72= Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3bytesTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Notification β Tffd700$Te6edf3parsedTa5d6ff" Tb4b4b4}
Te6edf3notificationChannelTb4b4b4.Te6edf3trySendTb4b4b4(Te6edf3parsedTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Te6edf3subscribedTb4b4b4.Te6edf3completeExceptionallyTb4b4b4(Te6edf3eTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Control Point notification errorTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Tff7b72thisTb4b4b4)
Te6edf3subscribedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
T8b949e// Conservative settle after CCCD confirmation before issuing commands.
Te6edf3delayTb4b4b4(Te6edf3SUBSCRIPTION_SETTLETb4b4b4)
T8b949e// Best-effort DFU Version read β gate out unsupported old bootloaders (SDK β€ 6).
Tff7b72val Te6edf3versionChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_VERSION_UUIDTb4b4b4)
Tff7b72val Te6edf3version Tff7b72=
Te6edf3safeCatching Tb4b4b4{ Te6edf3serviceTb4b4b4.Te6edf3readTb4b4b4(Te6edf3versionCharTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff2Tb4b4b4) Tb4b4b4(Te6edf3bytesTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3or Tb4b4b4(Tb4b4b4(Te6edf3bytesTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4) Tff7b72else Tff7b72-T79c0ff1
Tb4b4b4}
Tb4b4b4.Te6edf3getOrElse Tb4b4b4{ Tff7b72-T79c0ff1 Tb4b4b4}
Te6edf3dfuVersion Tff7b72= Te6edf3version
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: DFU Version characteristic = Tffd700$Te6edf3versionTa5d6ff (-1 β absent / unreadable)Ta5d6ff" Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3version Tff7b72in T79c0ff1.Tb4b4b4.Te6edf3MIN_SUPPORTED_DFU_VERSION Tff7b72- T79c0ff1Tb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3UnsupportedBootloaderTb4b4b4(Te6edf3versionTb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Connected and ready (Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 3: Init packet transfer (.dat)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Sends the legacy DFU init packet using the SDK 7+ extended-init flow:
* 1. `START_DFU [APP]` β device prepares.
* 2. Image sizes `[0u32, 0u32, appSize_u32]` on the Packet characteristic.
* 3. `INIT_PARAMS_START` β init bytes on Packet β `INIT_PARAMS_COMPLETE`.
*
* The legacy init packet for an APP image is typically 14 bytes (SDK 7) or 32 bytes (SDK 11 with signature). Any
* `.dat` significantly larger than that almost certainly belongs to a Secure DFU build that has been mis-packaged
* for a legacy bootloader β we surface that with a helpful error rather than letting the device reject it.
*
* The init packet is bracketed by START/COMPLETE, but the upload itself is intermixed with image sizes. To match
* Nordic's library, the [initPacket] argument here is the legacy init bytes; the firmware [transferFirmware] method
* needs to be called next to provide the actual image (and the size we include here must match its length).
*
* Because `transferInitPacket` is called before [transferFirmware], we don't yet know the firmware size when
* sending image sizes. To keep the [DfuUploadTransport] contract clean we instead send image sizes lazily inside
* [transferFirmware]'s START phase. **This method only writes START_DFU + brackets the init packet.** Any
* outstanding image-size write happens at the start of [transferFirmware].
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fftransferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3safeCatching Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3initPacketTb4b4b4.Te6edf3size Tff7b72> Te6edf3MAX_REASONABLE_LEGACY_INIT_SIZETb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3InitPacketNotLegacyTb4b4b4(Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Stashing init packet (Tffd700${Te6edf3initPacketTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes) for transfer in Phase 4.Ta5d6ff" Tb4b4b4}
Te6edf3pendingInitPacket Tff7b72= Te6edf3initPacket
Tb4b4b4}
T8b949e/** Init packet stashed by [transferInitPacket]; flushed at the start of [transferFirmware]. */
Tff7b72private Tff7b72var Te6edf3pendingInitPacketTb4b4b4: Te6edf3ByteArray? Tff7b72= Tff7b72null
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 4: Firmware transfer (.bin)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Drives the full upload sequence (START, init-packet brackets, PRN setup, firmware stream, validate, activate).
*
* Sequence details:
* 1. `START_DFU [0x04]` (APP image only).
* 2. Image sizes payload on Packet char: `[0u32, 0u32, firmware.size_u32]`.
* 3. Await START response.
* 4. `INIT_PARAMS_START`, init bytes on Packet, `INIT_PARAMS_COMPLETE`. Await init response.
* 5. `PRN_REQ [PRN_LE16]`. (No response.)
* 6. `RECEIVE_FIRMWARE_IMAGE`. (No response.)
* 7. Stream firmware in MTU-sized chunks. Every PRN packets, await `PacketReceipt(bytesReceived)` and verify count.
* 8. After last byte, await final response for `RECEIVE_FIRMWARE_IMAGE`.
* 9. `VALIDATE`, await response.
* 10. `ACTIVATE_AND_RESET` β an ordinary disconnect/write Exception may occur because the device resets before the
* acknowledgement; treat that operational Exception as expected success (structured cancellation and Error
* subtypes still propagate).
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fftransferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72=
Te6edf3safeCatching Tb4b4b4{
Tff7b72val Te6edf3initPacket Tff7b72=
Te6edf3pendingInitPacket
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6fftransferInitPacket must be called before transferFirmwareTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Starting upload (init=Tffd700${Te6edf3initPacketTb4b4b4.Te6edf3sizeTffd700}Ta5d6ffB, firmware=Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ffB)...Ta5d6ff" Tb4b4b4}
T8b949e// ββ 1. START_DFU + image sizes on Packet, then response βββββββββββββ
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3START_DFUTb4b4b4, Te6edf3LegacyDfuImageTypeTb4b4b4.Te6edf3APPLICATIONTb4b4b4)Tb4b4b4)
Te6edf3writePacketTb4b4b4(Te6edf3legacyImageSizesPayloadTb4b4b4(Te6edf3appSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4)
Te6edf3handleStartResponseTb4b4b4(Te6edf3awaitResponseTb4b4b4(Te6edf3START_RESPONSE_TIMEOUTTb4b4b4)Tb4b4b4)
T8b949e// ββ 2. INIT_PARAMS_START β init bytes on Packet β INIT_PARAMS_COMPLETE β response ββ
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_PARAMS_STARTTb4b4b4)Tb4b4b4)
Te6edf3writePacketChunkedTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_PARAMS_COMPLETETb4b4b4)Tb4b4b4)
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3INIT_DFU_PARAMSTb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3COMMAND_TIMEOUTTb4b4b4)Tb4b4b4)
T8b949e// Bump the BLE link to high-throughput mode (~7.5 ms interval) before streaming.
T8b949e// Default Android intervals (~30-50 ms) starve the link during sustained DFU and trigger LSTO. Mirrors
T8b949e// Nordic LegacyDfuImpl.java requestConnectionPriority(CONNECTION_PRIORITY_HIGH).
Tff7b72val Te6edf3highPriorityRequested Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3requestHighConnectionPriorityTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: requestHighConnectionPriority -> Tffd700$Te6edf3highPriorityRequestedTa5d6ff" Tb4b4b4}
T8b949e// ββ 3. PRN setup ββββββββββββββββββββββββββββββββββββββββββββββββββββ
Te6edf3writeControlPointTb4b4b4(Te6edf3legacyPrnRequestPayloadTb4b4b4(Te6edf3streamProfileTb4b4b4.Te6edf3prnIntervalPacketsTb4b4b4)Tb4b4b4)
T8b949e// ββ 4. RECEIVE_FIRMWARE_IMAGE ββββββββββββββββββββββββββββββββββββββ
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RECEIVE_FIRMWARE_IMAGETb4b4b4)Tb4b4b4)
T8b949e// ββ 5. Stream firmware βββββββββββββββββββββββββββββββββββββββββββββ
Te6edf3streamFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4, Te6edf3onProgressTb4b4b4)
T8b949e// ββ 6. Final RECEIVE_FIRMWARE_IMAGE response ββββββββββββββββββββββββ
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RECEIVE_FIRMWARE_IMAGETb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3VALIDATE_TIMEOUTTb4b4b4)Tb4b4b4)
T8b949e// ββ 7. VALIDATE ββββββββββββββββββββββββββββββββββββββββββββββββββββ
Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3VALIDATETb4b4b4)Tb4b4b4)
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3VALIDATETb4b4b4, Te6edf3awaitResponseTb4b4b4(Te6edf3VALIDATE_TIMEOUTTb4b4b4)Tb4b4b4)
T8b949e// ββ 8. ACTIVATE_AND_RESET ββββββββββββββββββββββββββββββββββββββββββ
T8b949e// The device may reset before the GATT write ACK lands; an ordinary disconnect/write Exception is expected
T8b949e// because of that reset β safeCatching treats it as success. Structured cancellation and Error subtypes
T8b949e// still propagate.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Sending ACTIVATE_AND_RESET (disconnect during write is expected)Ta5d6ff" Tb4b4b4}
Te6edf3safeCatching Tb4b4b4{ Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3ACTIVATE_AND_RESETTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3iTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: ACTIVATE write reported failure (expected on reset)Ta5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3onProgressTb4b4b4(T79c0ff1fTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Upload complete, device rebooting into new firmware.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Low-speed when the bootloader did not negotiate a larger MTU, leaving us on the 20-byte packet floor. Valid once
* [connectToDfuMode] has established the connection (the MTU is known by then).
*/
Tff7b72override Tff7b72val Te6edf3isLowSpeedTransferTb4b4b4: Tffa657Boolean
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3computeStreamPacketSizeTb4b4b4(Tb4b4b4) Tff7b72<Tff7b72= Te6edf3LEGACY_DFU_PACKET_SIZE
T8b949e/**
* Determine the per-packet size for firmware streaming.
*
* Uses the negotiated ATT MTU β 3 (the largest `WITHOUT_RESPONSE` write the link allows), capped at
* [MAX_HIGH_MTU_PACKET_SIZE] and floored to a 4-byte boundary. This is **self-gating**: a bootloader that cannot
* accept large DFU writes never negotiates a large MTU, so we fall back to the 20-byte [LEGACY_DFU_PACKET_SIZE]
* floor. The advertised name (`AdaDFU` vs OTAFIX `_DFU`) is NOT used β the negotiated MTU is the direct capability
* signal. The Adafruit nRF52 bootloader's DFU data characteristic accepts up to MTUβ3 = 244 bytes (it copies the
* actual write length into a 600-byte pool buffer), but **rejects any write whose length is not a multiple of 4**
* (`BLE_DFU_RESP_VAL_NOT_SUPPORTED`) β hence the word-alignment floor.
*/
Tff7b72private Tff7b72fun Td2a8ffcomputeStreamPacketSizeTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72val Te6edf3negotiated Tff7b72=
Te6edf3bleConnectionTb4b4b4.Te6edf3maximumWriteValueLengthTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4) Tff7b72?: Tff7b72return Te6edf3LEGACY_DFU_PACKET_SIZE
Tff7b72val Te6edf3sized Tff7b72= Te6edf3negotiatedTb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3LEGACY_DFU_PACKET_SIZETb4b4b4, Te6edf3MAX_HIGH_MTU_PACKET_SIZETb4b4b4)
Tff7b72return Te6edf3sized Tff7b72- Tb4b4b4(Te6edf3sized Tff7b72% Te6edf3DFU_PACKET_WORD_ALIGNMENTTb4b4b4)
Tb4b4b4}
T8b949e/**
* Stream [firmware] to the Packet characteristic under a single outer disconnect tripwire. The outer watcher is the
* sole streaming disconnect classifier β PRN waits inside the loop intentionally do NOT install another tripwire,
* so a link drop during a PRN wait surfaces as a typed [LegacyDfuException.MidStreamDisconnect] carrying host-side
* and confirmed-progress diagnostics, not a generic handshake-style [DfuException.ConnectionFailed].
*
* Every [streamProfile.prnIntervalPackets] packets the loop awaits a [LegacyDfuResponse.PacketReceipt] and verifies
* the bootloader's bytes-received count. The [streamOffset], [streamLastPrnOffset], and [streamLastPrnLatencyMs]
* snapshots give the watcher's onDrop callback visible diagnostic values.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNestedBlockDepthTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffstreamFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
T8b949e// Packet size = negotiated ATT MTU β 3, word-aligned and capped at 244 (see computeStreamPacketSize). Falls
T8b949e// back to 20 bytes when the bootloader did not negotiate a larger MTU, which is the self-gating safety against
T8b949e// bootloaders that can't accept large DFU writes β the 20-byte path is the slow but universally-safe default.
Tff7b72val Te6edf3mtu Tff7b72= Te6edf3computeStreamPacketSizeTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3prnInterval Tff7b72= Te6edf3streamProfileTb4b4b4.Te6edf3prnIntervalPackets
T8b949e// Reset stream progress for this attempt. These are @Volatile instance properties so the disconnect-tripwire
T8b949e// callback (running in a child coroutine on the caller's dispatcher) has a visible snapshot.
Te6edf3streamOffset Tff7b72= T79c0ff0
Te6edf3streamLastPrnOffset Tff7b72= Tff7b72-T79c0ff1
Te6edf3streamLastPrnLatencyMs Tff7b72= Tff7b72-T79c0ff1
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Streaming Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes with packet size Tffd700$Te6edf3mtuTa5d6ff Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff(advertised='Tffd700${Te6edf3dfuAdvertisedName Tff7b72?: Ta5d6ff"Tff7b72?Ta5d6ff"Tffd700}Ta5d6ff', dfuVersion=Tffd700$Te6edf3dfuVersionTa5d6ff, Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffprofile=Tffd700$Te6edf3streamProfileTa5d6ff, prnInterval=Tffd700$Te6edf3prnIntervalTa5d6ff)Ta5d6ff"
Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3withDisconnectTripwireTb4b4b4(
Te6edf3onDrop Tff7b72= Tb4b4b4{ Te6edf3state Tff7b72-Tff7b72>
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Link dropped mid-stream at host in-flight offset Tffd700$Te6edf3streamOffsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff(state=Tffd700$Te6edf3stateTa5d6ff, lastConfirmedPrn=Tffd700$Te6edf3streamLastPrnOffsetTa5d6ff, lastPrnLatencyMs=Tffd700$Te6edf3streamLastPrnLatencyMsTa5d6ff)Ta5d6ff"
Tb4b4b4}
Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3MidStreamDisconnectTb4b4b4(
Te6edf3bytesSent Tff7b72= Te6edf3streamOffsetTb4b4b4,
Te6edf3totalBytes Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3connectionState Tff7b72= Te6edf3stateTb4b4b4,
Te6edf3lastConfirmedBytes Tff7b72= Te6edf3streamLastPrnOffsetTb4b4b4,
Tb4b4b4)
Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72var Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4, Te6edf3timeout Tff7b72= Te6edf3STREAM_TIMEOUTTb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Tff7b72while Tb4b4b4(Te6edf3streamOffset Tff7b72< Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3end Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3streamOffset Tff7b72+ Te6edf3mtuTb4b4b4, Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)
T8b949e// Publish the in-flight boundary BEFORE invoking service.write() so the disconnect watcher
T8b949e// observes the current attempted chunk when a disconnect fires synchronously inside the write
T8b949e// (e.g. a test harness or a real bootloader that drops on receive). The write may not complete
T8b949e// and the host stack may not accept it; this boundary is the dispatched boundary, not a
T8b949e// confirmed one β the authoritative checkpoint is the PRN-confirmed offset (streamLastPrnOffset).
Tff7b72val Te6edf3chunk Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3copyOfRangeTb4b4b4(Te6edf3streamOffsetTb4b4b4, Te6edf3endTb4b4b4)
Te6edf3streamOffset Tff7b72= Te6edf3end
Tff7b72try Tb4b4b4{
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3chunkTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Write CANCELLED at offset Tffd700$Te6edf3streamOffsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff cause=Tffd700${Te6edf3eTb4b4b4.Te6edf3causeTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// The outer stream tripwire watches `connectionState` for Disconnected, but `service.write()`
T8b949e// can throw BEFORE the state-flow watcher processes the Disconnected emission. In that ordering
T8b949e// the raw write exception would escape without typed MidStreamDisconnect classification, so
T8b949e// later retries would not switch to the RECOVERY profile. Re-check the typed state here: if the
T8b949e// link is already Disconnected, classify as MidStreamDisconnect so the retry coordinator can
T8b949e// react. Error subtypes are NOT caught here β they propagate unchanged.
Tff7b72val Te6edf3state Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value
Tff7b72if Tb4b4b4(Te6edf3state Tff7b72is Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3MidStreamDisconnectTb4b4b4(
Te6edf3bytesSent Tff7b72= Te6edf3streamOffsetTb4b4b4,
Te6edf3totalBytes Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3connectionState Tff7b72= Te6edf3stateTb4b4b4,
Te6edf3lastConfirmedBytes Tff7b72= Te6edf3streamLastPrnOffsetTb4b4b4,
Te6edf3cause Tff7b72= Te6edf3eTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: Write FAILED for chunk ending at host in-flight offset Tffd700$Te6edf3streamOffsetTa5d6ff/Ta5d6ff" Tff7b72+
Ta5d6ff"Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff: Tffd700${Te6edf3eTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
Te6edf3packetsSincePrnTff7b72+Tff7b72+
Tff7b72if Tb4b4b4(Te6edf3packetsSincePrn Tff7b72>Tff7b72= Te6edf3prnInterval Tff7b72&Tff7b72& Te6edf3streamOffset Tff7b72< Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3awaitMark Tff7b72= Te6edf3TimeSourceTb4b4b4.Te6edf3MonotonicTb4b4b4.Te6edf3markNowTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Awaiting PRN at offset Tffd700$Te6edf3streamOffsetTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3receipt Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3awaitPacketReceiptDuringStreamTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: awaitPacketReceiptDuringStream CANCELLED at offset Tffd700$Te6edf3streamOffsetTa5d6ff Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffcause=Tffd700${Te6edf3eTb4b4b4.Te6edf3causeTffd700}Ta5d6ff"
Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
Tff7b72val Te6edf3latencyMs Tff7b72= Te6edf3awaitMarkTb4b4b4.Te6edf3elapsedNowTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3inWholeMilliseconds
Tff7b72if Tb4b4b4(Te6edf3latencyMs Tff7b72>Tff7b72= Te6edf3PRN_LATENCY_WARN_THRESHOLD_MSTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{
Ta5d6ff"Ta5d6ffLegacy DFU: PRN receipt latency Tffd700${Te6edf3latencyMsTffd700}Ta5d6ffms at offset Tffd700$Te6edf3streamOffsetTa5d6ff Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff(>= Tffd700${Te6edf3PRN_LATENCY_WARN_THRESHOLD_MSTffd700}Ta5d6ffms β possible bootloader backpressure or BLE scheduling delay)Ta5d6ff"
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: PRN receipt at offset Tffd700$Te6edf3streamOffsetTa5d6ff latency=Tffd700${Te6edf3latencyMsTffd700}Ta5d6ffmsTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72val Te6edf3expected Tff7b72= Te6edf3streamOffsetTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3receiptTb4b4b4.Te6edf3bytesReceived Tff7b72!Tff7b72= Te6edf3expectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3PacketReceiptMismatchTb4b4b4(Te6edf3expectedTb4b4b4, Te6edf3receiptTb4b4b4.Te6edf3bytesReceivedTb4b4b4)
Tb4b4b4}
T8b949e// Record the checkpoint only after the receipt validates β a mismatched PRN must
T8b949e// not be reported as the last successful checkpoint in the link-drop log.
Te6edf3streamLastPrnOffset Tff7b72= Te6edf3streamOffset
Te6edf3streamLastPrnLatencyMs Tff7b72= Te6edf3latencyMs
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Te6edf3onProgressTb4b4b4(Te6edf3streamOffsetTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Streamed Tffd700$Te6edf3streamOffsetTa5d6ff/Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes (lastPRN=Tffd700$Te6edf3streamLastPrnOffsetTa5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Abort & teardown
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Send `RESET` to the device, instructing it to discard any in-progress transfer and reboot. Best-effort β the
* device may disconnect before the write ACK lands; that's expected.
*
* The RESET op remains a `WITH_RESPONSE` write (the bootloader is contractually allowed to act on it before the
* GATT ACK lands, but we still request one so the link-layer queues it reliably), but we bound the wait with a
* short timeout. A missing acknowledgement is ambiguous: the device may have accepted RESET and rebooted, become
* unresponsive, disconnected, or simply failed to receive or complete the write. We report the outcome
* (acknowledged, unacknowledged, or operational failure) without claiming the RESET landed. Operational Exceptions
* are best-effort and non-fatal; structured-concurrency cancellation and Error subtypes propagate. The caller
* (`SecureDfuHandler`) tears the connection down afterwards regardless.
*
* Parent cancellation is preserved: a [CancellationException] that escapes `withTimeoutOrNull` is propagated.
*/
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffabortTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3write Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3withTimeoutOrNullTb4b4b4(Te6edf3RESET_WRITE_TIMEOUTTb4b4b4) Tb4b4b4{ Te6edf3writeControlPointTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3RESETTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: RESET write cancelled; disconnectingTa5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: RESET write failed; disconnectingTa5d6ff" Tb4b4b4}
Tff7b72return
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3write Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: RESET write acknowledged; disconnectingTa5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: RESET write unacknowledged within the timeout; disconnectingTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3safeCatching Tb4b4b4{ Te6edf3bleConnectionTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: Error during disconnectTa5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3transportScopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Low-level GATT helpers
T8b949e// ---------------------------------------------------------------------------
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwriteControlPointTb4b4b4(Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3controlCharTb4b4b4, Te6edf3payloadTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwritePacketTb4b4b4(Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3payloadTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Write [data] to the Packet char in 20-byte chunks. Legacy DFU bootloaders cap packet size at 20 bytes. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwritePacketChunkedTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LEGACY_DFU_PACKET_UUIDTb4b4b4)
Tff7b72var Te6edf3pos Tff7b72= T79c0ff0
Tff7b72while Tb4b4b4(Te6edf3pos Tff7b72< Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3end Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3pos Tff7b72+ Te6edf3LEGACY_DFU_PACKET_SIZETb4b4b4, Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3dataTb4b4b4.Te6edf3copyOfRangeTb4b4b4(Te6edf3posTb4b4b4, Te6edf3endTb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Te6edf3pos Tff7b72= Te6edf3end
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffawaitResponseTb4b4b4(Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4)Tb4b4b4: Te6edf3LegacyDfuResponse Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3timeoutTb4b4b4) Tb4b4b4{
T8b949e// Fail fast + accurately if the bootloader drops the link mid-handshake instead of answering.
T8b949e// Some Legacy bootloader variants disconnect on the first Control Point command
T8b949e// (e.g. stale-bond encryption mismatch); without this the receive() below just blocks until
T8b949e// `timeout`, so the user waited for the full command timeout and saw a misleading
T8b949e// "No response from Control Point" for what was really an immediate disconnect.
Te6edf3bleConnectionTb4b4b4.Te6edf3withDisconnectTripwireTb4b4b4(Te6edf3onDrop Tff7b72= Tff7b72::Te6edf3handshakeDropErrorTb4b4b4) Tb4b4b4{
T8b949e// Drain any stray PRNs that arrive before the response we want.
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3r Tff7b72= Te6edf3notificationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72!is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceiptTb4b4b4) Tff7b72returnTf0883e@withDisconnectTripwire Te6edf3r
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNREACHABLE_CODETa5d6ff"Tb4b4b4)
Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ffunreachableTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3currentCoroutineContextTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3ensureActiveTb4b4b4(Tb4b4b4)
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TimeoutTb4b4b4(Ta5d6ff"Ta5d6ffNo response from Legacy DFU Control Point after Tffd700$Te6edf3timeoutTa5d6ff"Tb4b4b4)
Tb4b4b4}
T8b949e/**
* PRN wait used inside [streamFirmware]. Deliberately does NOT install its own [withDisconnectTripwire]:
* [streamFirmware] owns the sole disconnect watcher during PRN waits, and a nested tripwire here would race it and
* could surface a generic [DfuException.ConnectionFailed] from [handshakeDropError] instead of the typed
* [LegacyDfuException.MidStreamDisconnect] that drives the recovery profile. The handshake-level disconnect watcher
* lives in [awaitResponse] (its [withDisconnectTripwire] classifies link drops during Control Point responses).
*
* A [TimeoutCancellationException] may be raised by an outer [withTimeout] (structured cancellation) rather than
* the local 30 s missing-PRN timeout. We re-check [ensureActive] first so parent/outer cancellation is rethrown,
* and only then surface the local [DfuException.Timeout].
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffawaitPacketReceiptDuringStreamTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceipt Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3COMMAND_TIMEOUTTb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3r Tff7b72= Te6edf3notificationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3PacketReceiptTb4b4b4) Tff7b72returnTf0883e@withTimeout Te6edf3r
Tff7b72if Tb4b4b4(Te6edf3r Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3FailureTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3rTb4b4b4.Te6edf3requestOpcodeTb4b4b4, Te6edf3rTb4b4b4.Te6edf3statusTb4b4b4)
Tb4b4b4}
T8b949e// Stray Success or Unknown β ignore.
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNREACHABLE_CODETa5d6ff"Tb4b4b4)
Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ffunreachableTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3currentCoroutineContextTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3ensureActiveTb4b4b4(Tb4b4b4)
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TimeoutTb4b4b4(Ta5d6ff"Ta5d6ffNo packet receipt notification after Tffd700$Te6edf3COMMAND_TIMEOUTTa5d6ff"Tb4b4b4)
Tb4b4b4}
T8b949e/** Error for a link drop while awaiting a Control Point response β distinguishes a disconnect from a true stall. */
Tff7b72private Tff7b72fun Td2a8ffhandshakeDropErrorTb4b4b4(Te6edf3stateTb4b4b4: Te6edf3BleConnectionStateTb4b4b4)Tb4b4b4: Te6edf3Throwable Tff7b72= Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(
Ta5d6ff"Ta5d6ffBLE link dropped during DFU handshake (state=Tffd700$Te6edf3stateTa5d6ff). The device disconnected before answering a Legacy DFU Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffcommand; some bootloader variants reboot to the app or drop the link in this state.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e/**
* Validate the `START_DFU` response, with special handling for `INVALID_STATE`.
*
* A device whose previous DFU session was interrupted (link drop, app closed mid-stream) keeps its half-finished
* transfer state and rejects a fresh `START_DFU` with `INVALID_STATE` until it is reset. This is the common case
* when *recovering* a device stranded in the bootloader.
*
* We do NOT try to RESET on this connection: some Legacy bootloader variants become unresponsive after returning
* INVALID_STATE. Skip a potentially blocking same-connection RESET and use the bounded fresh-connection reset-prime
* path instead. We fast-fail with [LegacyDfuException.StaleSessionReset]; [SecureDfuHandler] then resets the
* bootloader over a *fresh* connection (which is responsive up until START) before retrying. Mirrors the intent of
* Nordic `LegacyDfuImpl.resetAndRestart()`.
*/
Tff7b72private Tff7b72fun Td2a8ffhandleStartResponseTb4b4b4(Te6edf3responseTb4b4b4: Te6edf3LegacyDfuResponseTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3response Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3Failure Tff7b72&Tff7b72& Te6edf3responseTb4b4b4.Te6edf3status Tff7b72=Tff7b72= Te6edf3LegacyDfuStatusTb4b4b4.Te6edf3INVALID_STATETb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffLegacy DFU: START rejected with INVALID_STATE (stale session from an interrupted flash)Ta5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3StaleSessionResetTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3requireSuccessTb4b4b4(Te6edf3LegacyDfuOpcodeTb4b4b4.Te6edf3START_DFUTb4b4b4, Te6edf3responseTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffrequireSuccessTb4b4b4(Te6edf3expectedOpcodeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3responseTb4b4b4: Te6edf3LegacyDfuResponseTb4b4b4) Tb4b4b4{
Tff7b72when Tb4b4b4(Te6edf3responseTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3Success Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3requestOpcode Tff7b72!Tff7b72= Te6edf3expectedOpcodeTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffLegacy DFU response opcode mismatch: expected Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(T79c0ff2Tb4b4b4, Ta5d6ff'0'Tb4b4b4)Tffd700}Ta5d6ff, Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffgot 0xTffd700${Te6edf3responseTb4b4b4.Te6edf3requestOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(T79c0ff2Tb4b4b4, Ta5d6ff'0'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72is Te6edf3LegacyDfuResponseTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72>
Tff7b72throw Te6edf3LegacyDfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3responseTb4b4b4.Te6edf3requestOpcodeTb4b4b4, Te6edf3responseTb4b4b4.Te6edf3statusTb4b4b4)
Tff7b72else Tff7b72-Tff7b72>
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffUnexpected Legacy DFU response for opcode 0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff: Tffd700$Te6edf3responseTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Scanning
T8b949e// ---------------------------------------------------------------------------
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffscanForDeviceTb4b4b4(Te6edf3predicateTb4b4b4: Tb4b4b4(Te6edf3BleDeviceTb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3BleDevice? Tff7b72= Te6edf3scanForBleDeviceTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffLegacy DFUTa5d6ff"Tb4b4b4,
Te6edf3serviceUuid Tff7b72= Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4,
Te6edf3predicate Tff7b72= Te6edf3predicateTb4b4b4,
Tb4b4b4)
T8b949e// ---------------------------------------------------------------------------
T8b949e// Constants
T8b949e// ---------------------------------------------------------------------------
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3CONNECT_TIMEOUT Tff7b72= T79c0ff1T79c0ff5.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3COMMAND_TIMEOUT Tff7b72= T79c0ff3T79c0ff0.Te6edf3seconds
T8b949e/**
* Best-effort timeout around the Legacy `RESET` (`0x06`) Control-Point write. Legacy bootloaders may disconnect
* before the RESET write acknowledgement returns. A missing acknowledgement is ambiguous: the device may have
* accepted RESET and rebooted, become unresponsive, disconnected, or failed to receive/complete the write.
* Bound the acknowledgement wait to one second, report it as unacknowledged, then rely on connection teardown
* and the subsequent retry/re-advertisement flow.
*/
Tff7b72internal Tff7b72val Te6edf3RESET_WRITE_TIMEOUT Tff7b72= T79c0ff1.Te6edf3seconds
T8b949e/**
* Time to wait for the START_DFU response notification.
*
* The stock Adafruit nRF52 bootloader is single-bank: on START it erases the **entire** application bank (~800
* KB β 200 flash pages) before firing the START-procedure response, and because the BLE link is live the
* SoftDevice time-slices each page erase against radio events, stretching the erase to ~30-50 s. The old 30 s
* cap aborted mid-erase (killing an otherwise-healthy session); Nordic's own DFU library imposes no such short
* cap here. 90 s covers the worst-case erase with margin. The disconnect tripwire still fast-fails on a genuine
* link drop, so this only extends the *silent-but-connected* wait.
*/
Tff7b72private Tff7b72val Te6edf3START_RESPONSE_TIMEOUT Tff7b72= T79c0ff9T79c0ff0.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3VALIDATE_TIMEOUT Tff7b72= T79c0ff6T79c0ff0.Te6edf3seconds
Tff7b72private Tff7b72val Te6edf3SUBSCRIPTION_SETTLE Tff7b72= T79c0ff5T79c0ff0T79c0ff0.Te6edf3milliseconds
T8b949e/**
* Wall-clock budget for a full firmware streaming session. Must comfortably exceed the upload duration for the
* largest expected image at the slowest realistic Legacy DFU throughput (~1-3 KB/s with 20-byte packets). The
* per-receipt and per-write watchdogs inside the loop catch real stalls; this cap is just a safety net so a
* hung profile block can't sit forever.
*/
Tff7b72private Tff7b72val Te6edf3STREAM_TIMEOUT Tff7b72= T79c0ff1T79c0ff5.Te6edf3minutes
T8b949e/**
* Per-receipt latency threshold above which a PRN wait is logged at WARN. The Legacy bootloader normally ACKs
* each PRN window inside a few tens of milliseconds. A latency at or above this threshold is diagnostic only β
* it may indicate bootloader backpressure, flash activity, BLE scheduling delay, or degraded link conditions.
* It is not by itself a definite precursor to a supervision-timeout drop. Below this threshold receipts log at
* DEBUG.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3PRN_LATENCY_WARN_THRESHOLD_MS Tff7b72= T79c0ff1Te6edf3_000L
T8b949e/**
* Universally-safe Legacy DFU packet size (20 bytes β the original ATT_MTU minus the 3-byte ATT header). Used
* as the floor when the link did not negotiate a larger MTU; [computeStreamPacketSize] raises the per-packet
* size to the negotiated MTU (capped by [MAX_HIGH_MTU_PACKET_SIZE]) for a ~12Γ throughput win when available.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3LEGACY_DFU_PACKET_SIZE Tff7b72= T79c0ff2T79c0ff0
T8b949e/**
* Cap on the high-MTU DFU packet size. The largest ATT MTU the BLE 5.0 LE Data Length extension gives us is 247
* bytes (244 of payload); the Adafruit bootloader's DFU pool buffer (600 B) comfortably holds it, so 244 keeps
* each write to one ATT PDU. Stays a multiple of 4 β the bootloader rejects non-word-aligned writes.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3MAX_HIGH_MTU_PACKET_SIZE Tff7b72= T79c0ff2T79c0ff4T79c0ff4
T8b949e/** DFU data writes must be a whole number of 32-bit words; [computeStreamPacketSize] floors to this. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3DFU_PACKET_WORD_ALIGNMENT Tff7b72= T79c0ff4
T8b949e/** Minimum DFU Version we support; older bootloaders use the SDK β€ 6 single-shot init flow. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3MIN_SUPPORTED_DFU_VERSION Tff7b72= T79c0ff5
T8b949e/**
* Init packets larger than this are almost certainly Secure-DFU shaped (signed CBOR β 100-300 bytes) rather
* than legacy (14 B SDK 7 / 32 B SDK 11). 256 leaves comfortable headroom while still catching the obvious
* misuse case where a Secure `.dat` is fed into the Legacy path.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3MAX_REASONABLE_LEGACY_INIT_SIZE Tff7b72= T79c0ff2T79c0ff5T79c0ff6
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.2 - Generated in 0.12s